#include "jobs.h"

#include "refc_ptr.h"
#include "thread.h"
#include "functional_ptr.h"
#include "util.h"

#include <vector>
#include <queue>
#include <list>
#include <iostream>

using namespace krryn::core;

std::string job::get_name(){
	return typeid(*this).name();
}

class job_group::impl{
public:
	typedef std::vector<job::ptr> job_list_t;
	typedef std::vector<job_group*> simple_deps_list_t;

	job_list_t m_Jobs;
	simple_deps_list_t m_SimpleDeps;
	size_t m_CompletedCounter;
	mutex m_Guard;

	impl() : m_CompletedCounter(0) {}

	void notify_job_done(job_manager &a_Manager, job_group::group_job& a_Job){
		locker<mutex> l_Section(m_Guard);

		m_CompletedCounter++;

		// todo the 'any' dependencies

		if(m_CompletedCounter == m_Jobs.size()){
			simple_deps_list_t::iterator i;
			for(i = m_SimpleDeps.begin(); i != m_SimpleDeps.end(); i++){
				(*i)->add_to(a_Manager);
			}

			m_SimpleDeps.erase(m_SimpleDeps.begin(), m_SimpleDeps.end());
		}
	}
};

class job_group::group_job : public job{
public:
	group_job(job_group::impl* a_Impl, job_manager &a_Manager, job::ptr a_Job) 
		: m_Job(a_Job), m_Impl(a_Impl), m_Manager(a_Manager){
	
		int i = 0;
	}

	virtual void execute(){
		m_Job->execute();
	}

	virtual void done(){
		m_Job->done();
		m_Impl->notify_job_done(m_Manager, *this);
	}

	virtual std::string get_name(){
		return job::get_name() + " (" + m_Job->get_name() + ")";
	}

	job::ptr m_Job;
	job_group::impl* m_Impl;
	job_manager &m_Manager;
};

job_group::job_group(){
	m_Pimpl = new impl();
}

job_group::~job_group(){
	delete m_Pimpl, m_Pimpl = 0;
}

void job_group::add(job::ptr a_Job){
	m_Pimpl->m_Jobs.push_back(a_Job);
}

void job_group::add_to(job_manager &a_Manager){
	impl::job_list_t::iterator i;
	for(i = m_Pimpl->m_Jobs.begin(); i != m_Pimpl->m_Jobs.end(); i++){
		a_Manager.add(new group_job(m_Pimpl, a_Manager, *i));
	}
}

void job_group::depends_on_all_of(job_group::ptr a_Group){
	a_Group->m_Pimpl->m_SimpleDeps.push_back(this);
}

void job_group::depends_on_any_of(size_t a_Amount, job_group::ptr a_Group){
}

class job_manager::job_thread : public thread{
public:
	typedef unsigned long thread_id_t;
	typedef refc_ptr<job_thread> ptr;

	explicit job_thread(job_manager &a_Manager) : m_Id(m_IdCounter++), m_Manager(a_Manager){
		std::clog << "Starting job thread #" << m_Id << std::endl;
	}

	virtual void run(){
		do{
			// thread must continue running even when 
			// an exception occours inside of the job
			try{
				if(job::ptr l_Job = m_Manager.next()){
					{
						locker<mutex> l_Section(m_Guard);
						std::cout << "Starting " << l_Job->get_name() << std::endl;
					}

					l_Job->execute();
					l_Job->done();
				}
			}catch(const std::exception &a_Exception){
				locker<mutex> l_Section(m_Guard);
				std::cout << "Unhandled exception '" 
					<< a_Exception.what() << "'" << std::endl;
			}catch(...){
				locker<mutex> l_Section(m_Guard);
				std::cout << "Unknown unhandled exception" << std::endl;
			}
		}while(true);
	}

private:
	job_manager &m_Manager;

	thread_id_t m_Id;
	static mutex m_Guard;
	static thread_id_t m_IdCounter;
};

class job_manager::impl{
public:
	impl(job_manager &a_Manager, size_t a_NumberOfThreads) 
		: m_Threads(a_NumberOfThreads)
	{
		std::generate(m_Threads.begin(), m_Threads.end(), 
			ctor1<job_thread>(ref(a_Manager)));
	}

	void start(){
		std::for_each(m_Threads.begin(), m_Threads.end(), 
			mem_fun_ptr<job_thread::ptr>(&job_thread::start));
	}

	~impl(){
		std::for_each(m_Threads.begin(), m_Threads.end(), 
			mem_fun_ptr<job_thread::ptr>(&job_thread::kill));
	}

	typedef std::queue<job::ptr> job_list_t;
	typedef std::list<job_thread::ptr> thread_list_t;

	job_list_t m_Jobs;
	thread_list_t m_Threads;
	mutex m_Guard;
};

krryn::core::mutex job_manager::job_thread::m_Guard = krryn::core::mutex();
job_manager::job_thread::thread_id_t job_manager::job_thread::m_IdCounter = 0;

job_manager::job_manager(size_t a_NumberOfThreads){
	m_Pimpl = new impl(*this, a_NumberOfThreads);
	m_Pimpl->start();
}

job_manager::~job_manager(){
	delete m_Pimpl;
	m_Pimpl = 0;
}

void job_manager::add(job::ptr a_Job){
	locker<mutex> l_Section(m_Pimpl->m_Guard);

	m_Pimpl->m_Jobs.push(a_Job);
}

void job_manager::add(job_group::ptr a_Group){
	a_Group->add_to(*this);
}

job::ptr job_manager::next(){
	locker<mutex> l_Section(m_Pimpl->m_Guard);

	if(!m_Pimpl->m_Jobs.empty()){
		job::ptr l_Job = m_Pimpl->m_Jobs.front();
		m_Pimpl->m_Jobs.pop();
		return l_Job;
	}

	return 0;
}
